/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * The software in this package is published under the terms of the GPL v2.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE-GPL.txt file.
 */

package org.cemantika.aspects;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.List;

import org.aspectj.lang.annotation.SuppressAjWarnings;
import org.cemantika.contextsource.ContextSourceAdapter;
import org.cemantika.exception.RegisterContextSourceException;
import org.cemantika.management.ContextManager;
import org.cemantika.metamodel.structure.AcquisitionAssociation;
import org.cemantika.metamodel.structure.ContextSource;
import org.cemantika.model.ContextSourceModel;
import org.cemantika.util.CemantikaLogger;

/**
 * ContextSourceRegistration represents the aspect that registers a context source into the
 * {@link ContextManager}.
 * 
 * @author andersonfellipe
 */
@SuppressWarnings("unchecked")
public aspect ContextSourceRegistration {

    /**
     * Represents the call to the setContextSource method of a class that implements
     * {@link ContextSourceAdapter}.
     * 
     * @param contextSourceAdapter the object that implements {@link ContextSourceAdapter}.
     * @param obj the object of the context source to register.
     */
    pointcut setContextSource(ContextSourceAdapter contextSourceAdapter, Object obj): 
        this(contextSourceAdapter) && 
        execution(void ContextSourceAdapter.setContextSource(Object)) && 
        args(obj);

    /**
     * Represents the around advice that registers the object that implements
     * {@link ContextSourceAdapter} into the {@link ContextManager}'s context source list.
     * 
     * @param contextSourceAdapter the object that implements {@link ContextSourceAdapter}.
     * @param obj the object of the context source to register.
     */
    @SuppressWarnings("restriction")
    @SuppressAjWarnings( {"adviceDidNotMatch"})
    void around(ContextSourceAdapter contextSourceAdapter, Object obj): setContextSource(contextSourceAdapter, obj) {
        try {
            Annotation annotation = obj.getClass().getAnnotation(ContextSource.class);
            if (annotation instanceof ContextSource) {
                ContextManager contextManager = ContextManager.getInstance();
                List<AcquisitionAssociation> fieldAnnotations = new LinkedList<AcquisitionAssociation>();
                for (Field f : obj.getClass().getDeclaredFields()) {
                    AcquisitionAssociation acquisitionAssociation = f.getAnnotation(AcquisitionAssociation.class);
                    if (acquisitionAssociation != null) {
                        fieldAnnotations.add(acquisitionAssociation);
                    }
                }
                if (fieldAnnotations.isEmpty()) {
                    RegisterContextSourceException err =
                            new RegisterContextSourceException(
                                    "The context source registered must have one @AcquisitionAssociation annotation.");
                    CemantikaLogger.logger.error("** " + err.getMessage());
                    throw err;
                } else if (fieldAnnotations.size() > 1) {
                    RegisterContextSourceException err =
                            new RegisterContextSourceException(
                                    "The context source registered must have ONLY one @AcquisitionAssociation annotation.");
                    CemantikaLogger.logger.error("** " + err.getMessage());
                    throw err;
                } else {
                    String contextSourceType = obj.getClass().getSimpleName();
                    ContextSourceModel contextSource =
                            new ContextSourceModel(contextSourceAdapter, fieldAnnotations.get(0).element(),
                                    contextSourceType);
                    contextManager.addContextSource(contextSource);
                    CemantikaLogger.logger.info("** Context source '" + contextSourceType
                            + "' registered for the element '" + fieldAnnotations.get(0).element() + "' into "
                            + contextSourceAdapter.getClass().getSimpleName());
                }
                proceed(contextSourceAdapter, obj);
            } else {
                RegisterContextSourceException err =
                        new RegisterContextSourceException(
                                "The context source registered must have the @ContextSource annotation.");
                CemantikaLogger.logger.error("** " + err.getMessage());
                throw err;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
